package demo.db.elasticSearch.highClient;

import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.admin.indices.alias.Alias;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * 索引
 * <p>
 * 为了防止索引创建的太多导致的内存不足，对索引的数量可以进行修改。
 * <p>
 * <li>index.mapping.total_fields.limit：
 * 索引中的最大字段数。默认值为1000。
 * <li>index.mapping.depth.limit：
 * 字段的最大深度，以内部对象的数量来衡量。例如，如果所有字段都是在根对象级别定义的，则深度为1。如果有一个对象映射，则深度为 2，等等。默认值为20。
 * <li>index.mapping.nested_fields.limit：
 * nested索引中的最大字段数，默认为50。使用100个嵌套字段索引1个文档实际上索引101个文档，因为每个嵌套文档都被索引为单独的隐藏文档。
 *
 * <p>
 * <h1>mappings中的参数<h1/>
 *
 * <li>type(类型)
 * <li>analyzer(分析器)   作用是分词和词条标准化（比如dogs标准化为dog,大写标准化小写），默认是standard分析器，还可以在创建mapping时配置自定义分析器，并使用自定义分析器。
 * <li>normalizer  与analyzer类似，但是分词结果都是单一词
 * <li>boost      设置查询相关性权重，默认是1
 * <li>coerce   是否强制转换，默认为true，比如integer类型参数可以直接传递字符串数字，会自动转为数字。设置为false则不会强转，则参数值传递字符串会报错。
 * <li>copy_to  可将字段指向一个组别，之后的查询可直接查询该组别，查询范围会从组别包含的字段中进行查询，多个值用空格隔开，类似_all。
 * <li>doc_values  一般与keyword结合使用，默认为true,即查询可通过该字段进行排序和聚合查询，但是设置为fasle则不可通过该字段排序和聚合查询，但是相应会比较节省内存空间。
 * <li>dynamic   默认为true,es默认可以动态新增字段，改为false后，可以动态插入不存在的字段名，但是这个字段名只会在_source中显示，并不会在mapping中插入,不能通过索引进行查询。该参数与properties参数同级
 * <li>enabled   默认为true，设置为false则该字段不可被索引
 * <li>eager_global_ordinals 设置为true可以提高查询速度，但会降低更新速度，只可用在keyword类型使用，text类型只能在设置fileddata参数为ture时使用
 * <li>fileddata  与text配合使用，默认text类型不可支持排序和聚合查询，设置fileddata后即可，实现效果与新增keyword类型fields一致。
 * <li>format  一般与日期类型一起使用，格式化日期
 * <li>ignore_malformed   忽略异常插入的数据，默认为false，即插入异常数据会报错，比如integer类型插入非数字字符串，则会报错，设置为true则允许错误数据，可以插入。
 * <li>ignore_above   限制字段字符串长度,只能和keyword类型一起用，插入的数据超过限制数的数据不报错，但是不会被存储，搜索不到
 * <li> index   默认为true,设置为false则该字段不会存索引，即不可被搜索到。
 * <li> fields   为一个字段设定一个子字段，一般是由于当前字段数据类型不满足某种查询时使用，比如text类型的字段，想要排序就需要设置keyword类型的fields   通过此field进行排序，
 * <li>norms 规范化，默认为ture,如果该字段不参与计分，则可以设置为false以节省硬盘空间
 * <li>null_value 指定该字段为null或空数组时对应的索引值，默认null不可被索引，
 * <li>position_increment_gap 词间差距，当一个字段有多个值，默认该值为100,如下,因为Abraham 和 Lincoln分别在数组的两个词中，因此他们的伪差距为100，所以搜索不出来
 * <li>properties 配置type字段或子字段，自字段可如下配置嵌套类型和object
 * <li>search_analyzer   配置字段查询分析器，与analyzer一样，但只用在搜索时，同时存在则搜索以这个为主（注  es新增数据索引和搜索都是通过指定分析器进行分词）
 *
 *
 * <li>similarity  配置字段相关度计算算法，默认是BM25,还可配置classic（TF/IDF算法），boolean（不计算相关度，只看查询内容是否完全匹配）
 *
 *
 * <li>store   是否存储源数据，默认是true，即会存储源数据，设置为false则该字段不保存数据，一般用于映射
 * <pre>
 * 其实不管我们将store值设置为true或false，elasticsearch都会将该字段存储到Field域中；但是他们的区别是什么？
 *  （1）store = false时，默认设置；那么给字段只存储在"_source"的Field域中；
 *  （2）store = true时，该字段的value会存储在一个跟_source平级的独立Field域中；同时也会存储在_source中，所以有两份拷贝。
 *   那么我们在什么样的业务场景下使用store field功能？
 *   （1）_source field在索引的mapping 中disable了。这种情况下，如果不将某个field定义成store=true，那些将无法在返回的查询结果中看到这个field。
 *   （2）_source的内容非常大。这时候如果我们想要在返回的_source document中解释出某个field的值的话，开销会很大
 *   （当然你也可以定义source filtering将减少network overhead），
 *   比例某个document中保存的是一本书，所以document中可能有这些field: title, date, content。
 *   假如我们只是想查询书的title 跟date信息，而不需要解释整个_source（非常大），这个时候我们可以考虑将title, date这些field设置成store=true。
 *   需要注意的是，看起来将field store可以减少查询的开销，但其实这样也会加大disk的访问频率。
 *   假如你将_source中的10个field都定义store，那么在你查询这些field的时候会将会有10次disk seek的操作。
 *   而返回_source只有一次disk seek的操作。所以这个也是我们在定义的时候需要blance的。
 * </pre>
 *
 * <li>term_vector  词条向量，默认为false  参数有以下几个
 * <pre>
 *     no  默认，不存储词条向量
 *    yes   分词对应的字段会被存储
 *    with_positions   分词和每个词的位置会被存储
 *    with_offsets      分词和分词的起始和结束字符偏移量会被存储
 *    with_positions_offsets    分词 位置和向量会被存储
 * </pre>
 *
 *
 * <p>
 */
public class IndexDemo {

    private static Logger logger = LoggerFactory.getLogger(IndexDemo.class);


    /**
     * 分片设置
     *
     * @param request
     * @param shards
     * @param replicas
     */
    public static void buildSetting(CreateIndexRequest request, int shards, int replicas) {
        request.settings(Settings.builder()
                .put("index.number_of_shards", shards)
                .put("index.number_of_replicas", replicas));
    }

    /**
     * 创建索引的同时指定特殊字段的类型。
     * 指定了name的类型为keyword,这样name字段就可以用term来精准搜索了，
     * long类型的age就可以用来计算和比较大小了，
     * birthday指定为日期类型，其可以是format中的格式化类型也可以是时间戳类型，
     * 其他的没有指定类型的字段将以默认的映射来在es中建立字段。
     * <pre>
     *  PUT /gunspoc
     * {
     *   "mappings": {
     *     "doc":{
     *       "properties":{
     *         "name":{
     *           "type":"keyword"
     *         },
     *         "age":{
     *           "type": "long"
     *         },
     *         "address":{
     *           "type":"text"
     *         },
     *         "birthday":{
     *            "type": "date",
     *            "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
     *         }
     *       }
     *     }
     *   }
     * }
     * </pre>
     * <p>
     * <li>string  字符串类型
     * text and keyword
     * text:大文本，长字符串，可以通过指定的分词器进行分词就，建立倒排索引
     * keyword:不能够通过分词器进行分词，整个文本作为索引记录
     * <p>
     * <li>Numeric datatypes 数字数据类型：
     * long, integer, short, byte, double, float, half_float, scaled_float
     * <p>
     * <li>Date datatype  日期类型：  date
     * <p>
     * <li>Boolean datatype  布尔类型：   boolean
     * <p>
     * <li>Binary datatype  二进制数据类型：  binary
     * <p>
     * <li>Range datatypes  范围数据类型： integer_range, float_range, long_range, double_range, date_range
     * <p>
     * <li>Complex datatypesedit  复杂数据类型
     * <p>
     * <li>Object datatype  对象数据类型
     * object for single JSON objects   对于单个JSON对象
     * <p>
     * <li>Nested datatype  嵌套数据类型：nested for arrays of JSON objects  对于JSON对象的数组
     * <p>
     * <li>Geo datatypesedit  地理位置数据类型
     * Geo-point datatype  地理点数据类型
     * geo_point for lat/lon points   对于纬度/经度点  可存入对象json,字符串,数组
     * Geo-Shape datatype  Geo-Shape数据类型
     * geo_shape for complex shapes like polygons   对于像多边形这样的复杂形状
     * <p>
     * <li>Specialised datatypesedit  专用数据类型
     * <p>
     * <li>IP datatype   IP数据类型
     * ip for IPv4 and IPv6 addresses   用于IPv4和IPv6地址
     * <p>
     * <li>Completion datatype  完成数据类型
     * completion to provide auto-complete suggestions  提供自动完成的建议
     * <p>
     * <li>Token count datatype  令牌计数数据类型
     * token_count to count the number of tokens in a string  计算字符串中的标记数
     * <p>
     * <li>mapper-murmur3
     * murmur3 to compute hashes of values at index-time and store them in the index
     * 在索引时计算值的哈希值并将它们存储在索引中
     * <p>
     * <li>Percolator type  过滤器类型
     * Accepts queries from the query-dsl 接受来自query-dsl的查询
     * <p>
     * <li>join datatype  join 数据类型
     * Defines parent/child relation for documents within the same index
     * 为同一索引中的文档定义父/子关系
     *
     * @param request
     * @param type
     * @throws IOException
     */
    private void buildIndexMapping(CreateIndexRequest request, String type) throws IOException {

        XContentBuilder mappingBuilder = JsonXContent.contentBuilder()
                .startObject()
                .startObject("properties")

                .startObject("name")
                .field("type", "keyword")
                .endObject()

                .startObject("age")
                .field("type", "integer")
                .endObject()

                .startObject("address")
                .field("type", "text")
                .endObject()

                .startObject("birthday")
                .field("type", "date")
                .field("format", "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis")
                .endObject()


                .endObject()
                .endObject();
        request.mapping(type, mappingBuilder);
    }

    /**
     * 检查索引
     *
     * @return
     * @throws IOException
     */
    public void checkIndexExist() throws IOException {
        String index = "slow_redis_index_alias";
        RestHighLevelClient client = RestHighLevelClientDemo.getClient();
    }

    /**
     * 创建索引
     *
     * <pre>
     * {
     * "settings" : {
     * "number_of_shards" : 1,
     * "number_of_replicas" : 0
     * },
     * "mappings" : {
     * "_doc" : {
     * "properties" : {
     * "message" : { "type" : "text" }
     * }
     * }
     * },
     * "aliases" : {
     * "twitter_alias" : {}
     * }
     * }
     * </pre>
     */
    @Test
    public void createIndexDemo() {
        try (RestHighLevelClient client = RestHighLevelClientDemo.getClient()) {
            // 1、创建 创建索引request 参数：索引名mess
            CreateIndexRequest request = new CreateIndexRequest("mess");
            // 2、设置索引的settings
            request.settings(Settings.builder().put("index.number_of_shards", 3) // 分片数
                    .put("index.number_of_replicas", 2) // 副本数
                    .put("analysis.analyzer.default.tokenizer", "ik_smart") // 默认分词器
            );

            // 3、设置索引的mappings
            {// 方式1:JSON
                request.mapping("_doc", " {\"_doc\":{\"properties\":{\"message\":{\"type\":\"text\"}}}}",
                        XContentType.JSON);
            }

            {// 方式2: MAP
                Map<String, Object> jsonMap = new HashMap<>();
                Map<String, Object> message = new HashMap<>();
                message.put("type", "text");
                Map<String, Object> properties = new HashMap<>();
                properties.put("message", message);
                Map<String, Object> mapping = new HashMap<>();
                mapping.put("properties", properties);
                jsonMap.put("_doc", mapping);
                request.mapping("_doc", jsonMap);
            }

            {// 方式3: XContentBuilder
                XContentBuilder builder = XContentFactory.jsonBuilder();
                builder.startObject();
                {
                    builder.startObject("_doc");
                    {
                        builder.startObject("properties");
                        {
                            builder.startObject("message");
                            {
                                builder.field("type", "text");
                            }
                            builder.endObject();
                        }
                        builder.endObject();
                    }
                    builder.endObject();
                }
                builder.endObject();
                request.mapping("_doc", builder);
            }

            // 4、 设置索引的别名
            request.alias(new Alias("mmm"));

            // 5、 发送请求
            // 5.1 同步方式发送请求
            CreateIndexResponse createIndexResponse = client.indices().create(request, RequestOptions.DEFAULT);

            // 6、处理响应
            boolean acknowledged = createIndexResponse.isAcknowledged();
            boolean shardsAcknowledged = createIndexResponse.isShardsAcknowledged();
            System.out.println("acknowledged = " + acknowledged);
            System.out.println("shardsAcknowledged = " + shardsAcknowledged);

        } catch (IOException e) {
            logger.error("", e);
        }
    }

    /**
     * 创建异步索引
     */
    public static void createIndexDemoAsync() {
        try (RestHighLevelClient client = RestHighLevelClientDemo.getClient()) {

            // 1、创建 创建索引request 参数：索引名mess
            CreateIndexRequest request = new CreateIndexRequest("mess");

            // 2、设置索引的settings
            request.settings(Settings.builder().put("index.number_of_shards", 3) // 分片数
                    .put("index.number_of_replicas", 2) // 副本数
                    .put("analysis.analyzer.default.tokenizer", "ik_smart") // 默认分词器
            );

            // 3、设置索引的mappings
            request.mapping("_doc", " {\"_doc\":{\"properties\":{\"message\":{\"type\":\"text\"}}}}",
                    XContentType.JSON);

            // 4、 设置索引的别名
            request.alias(new Alias("mmm"));

            // 5、 发送请求
            // 5.1 异步方式发送请求

            ActionListener<CreateIndexResponse> listener = new ActionListener<CreateIndexResponse>() {

                @Override
                public void onResponse(CreateIndexResponse createIndexResponse) { // 6、处理响应 boolean
                    boolean acknowledged = createIndexResponse.isAcknowledged();
                    boolean shardsAcknowledged = createIndexResponse.isShardsAcknowledged();
                    System.out.println("acknowledged = " + acknowledged);
                    System.out.println("shardsAcknowledged = " + shardsAcknowledged);
                }

                @Override
                public void onFailure(Exception e) {
                    System.out.println("创建索引异常：" + e.getMessage());
                }
            };

            client.indices().createAsync(request, RequestOptions.DEFAULT, listener);

        } catch (IOException e) {
            logger.error("", e);
        }
    }

}
